class: title-slide, center, middle #.title[Introduzione a R] #.subtitle[Giornata 4 - Programmazione in R] <img src="data:image/png;base64,#img/arca_logo.svg" width="10%" style="display: block; margin: auto;" /> ###.location[Corsi ARCA - @DPSS] ###.author[Filippo Gambarota] --- # Programmazione in R Quello che vedremo in questa sezione sono i principali **costrutti della programmazione** e la loro applicazione in R. Ci sono alcuni punti da considerare: - Sono concetti trasversali estremamente utili - Sono alla base di qualcunque **funzionalità già implementata in R** - Vi permettono di fare qualunque cosa con il linguaggio --- # Programmazione in R - Disclaimer Ci sono delle cose che per tempo e complessità non possiamo affrontare e che sono R specifiche. Per questi aspetti avanzati del linguaggio, il libro [**Advanced R**](https://adv-r.hadley.nz/) è la cosa migliore <img src="data:image/png;base64,#img/adv_R.png" width="20%" style="display: block; margin: auto;" /> --- class: section, center, middle # Costrutti della programmazione in R --- # Costrutti della programmazione in R - Funzioni - Programmazione condizionale - Programmazione iterativa --- # Funzioni Analogalmente alle *funzioni matematiche* la funzione in programmazione consiste nell' **astrarre** una serie di operazioni (nel nostro caso una porzione di codice) definendo una serie di operazioni che forniti degli *input* forniscono degli *output* eseguendo una serie di *operazioni* --- # Funzioni Prendiamo l'equazione di una retta: `\(y = 2x + 3\)` dove `\(3\)` .pull-left[ ```r x <- 1:10 y <- 2*x + 3 ``` ] .pull-right[ <img src="data:image/png;base64,#4_programmazione_files/figure-html/unnamed-chunk-4-1.png" width="2100" style="display: block; margin: auto;" /> ] --- # Funzioni Se vogliamo *astrarre* questa operazione in modo da renderla più generale e utile dobbiamo definire: - **argomenti funzione**: quelle che in matematica sono le *variabili* - **corpo funzione**: le **operazioni** che la funzione deve eseguire usando gli argomenti - **output funzione**: cosa la funzione deve **restituire** come risultato --- # Funzioni - Argomenti Gli **argomenti** sono quelle parti variabili della funzione che vengono definiti e poi sono necessari ad eseguire la funzione stessa. Se vogliamo *astrarre* la retta che abbiamo visto prima dobbiamo definire alcune parti come **variabili**: La retta `\(y = mx + q\)` dove `\(m\)` è la pendenza, `\(x\)` sono i valori della variabile `\(x\)` e `\(q\)` è l'intercetta (valore di `\(y\)` quando `\(x = 0\)`). Quindi possiamo definire in R: ```r retta <- function(m, x, q){ # argomenti # body # output } ``` --- # Funzioni - Body Il corpo della funzione sono le operazioni da eseguire utilizzando gli argomenti in input. Nel caso della retta semplicemente moltiplicare `\(m\)` per ogni valore di `\(x\)` e aggiungere `\(q\)`. In questo modo otteniamo tutti i valori di `\(y\)`: ```r retta <- function(m, x, q){ # argomenti y <- m*x + q # output } ``` --- # Funzioni - Output L'output è il **risultato che la funzione ci restituisce** dopo aver eseguito tutte le operazioni. Nel nostro caso della retta, vogliamo ottenere il rispettivo valore di `\(y\)` per ogni valore di `\(x\)` inserito: ```r retta <- function(m, x, q){ # argomenti y <- m*x + q return(y) # restituisce y } ``` --- # Funzioni - Risultato finale .pull-left[ ```r retta <- function(m, x, q){ # argomenti y <- m*x + q return(y) # restituisce y } x <- 1:10 m <- 0.3 q <- 0 y <- retta(m, x, q) ``` ] .pull-right[ <img src="data:image/png;base64,#4_programmazione_files/figure-html/unnamed-chunk-9-1.png" width="2100" style="display: block; margin: auto;" /> ] --- class: section, center, middle # Programmazione condizionale --- # Programmazione condizionale In programmazione solitamente è necessario non solo eseguire una serie di operazione **MA** eseguire delle operazione in funzione di alcune **condizioni** Facciamo un esempio pratico, la funzione `summary()` in R fornisce un risultato diverso in base al tipo di input. Come è possibile tutto questo? Tramite l'utilizzo di **condizioni**: ```r x <- 1:10 # vettore numerico y <- factor(rep(c("a", "b", "c"), each = 10)) # vettore di stringhe summary(x) ``` ``` ## Min. 1st Qu. Median Mean 3rd Qu. Max. ## 1.00 3.25 5.50 5.50 7.75 10.00 ``` ```r summary(y) ``` ``` ## a b c ## 10 10 10 ``` --- # Programmazione condizionale Anche se non sappiamo quali operazioni svolga la funzione `summary()` possiamo immaginare una cosa simile ```r summary <- function(argomento){ # se l'argomento è un vettore numerico # esegui --> operazioni a,b,c # se l'argomento è un vettore stringa # esegui --> operazioni d,e,f # ... } ``` --- # Programmazione condizionale Il concetto di `se <condizione> allora fai <operazione>` si traduce in programmazione tramite quelli che si chiamano `if statement`: <img src="data:image/png;base64,#img/if_chart.png" width="40%" style="display: block; margin: auto;" /> --- # Programmazione condizionale Per lavorare con gli `if statements` dobbiamo avere chiaro: - il concetto di *operatori logici* ovvero `TRUE` e `FALSE` - il concetto di *operazioni logiche* `TRUE and TRUE = TRUE` --- # Programmazione condizionale Quando una sola condizione non basta... <img src="data:image/png;base64,#img/ifelse_chart.png" width="40%" style="display: block; margin: auto;" /> --- # Programmazione condizionale Per poter capire quale struttura condizionale utilizzare è importante capire bene il problema che dobbiamo risolvere. Ritornando all'esempio della funzione `summary()`, immaginiamo di avere 2 tipi di dati in R; stringhe e numeri. In questo caso è sufficiente avere un `if statement` che controlla se l'elemento è una stringa/numero e per tutto il resto applicare l'opposto. --- # Programmazione condizionale - Tip Esiste una famiglia di funzioni con prefisso `is.*` che fornisce `TRUE` quando la tipologia di oggetto corrisponde a quella richiesta e `FALSE` in caso contrario. ```r x <- 1:10 is.numeric(x) ``` ``` ## [1] TRUE ``` ```r is.factor(x) ``` ``` ## [1] FALSE ``` ```r is.character(x) ``` ``` ## [1] FALSE ``` Possiamo usare queste funzioni per creare un flusso condizionale nella nostra funzione `summary()` --- # Programmazione condizionale Scriviamo una funzione che restituisca la `media` quando il vettore è numerico e la tabella di frequenza (con la funzione `table()`) ```r my_summary <- function(x){ # testiamo la condizione if(is.numeric(x)){ return(mean(x)) }else{ return(table(x)) } } x <- 1:10 my_summary(x) ``` ``` ## [1] 5.5 ``` ```r x <- rep(c("a","b","c"), c(10, 2, 8)) my_summary(x) ``` ``` ## x ## a b c ## 10 2 8 ``` --- # `ifelse()` Un limite di usare gli `if statements` riguarda il fatto che funzionano solo su un singolo valore (i.e. non sono **vettorizzati**): ```r x <- 1:10 if(x < 5){ print("x è minore di 5") }else{ print("x è maggiore di 5") } ``` ``` ## Error in if (x < 5) {: the condition has length > 1 ``` La versione vettorizzata è la funzione `ifelse(test, yes, no)`: ```r ifelse(x < 5, "x è minore di 5", "x è maggiore di 5") ``` ``` ## [1] "x è minore di 5" "x è minore di 5" "x è minore di 5" ## [4] "x è minore di 5" "x è maggiore di 5" "x è maggiore di 5" ## [7] "x è maggiore di 5" "x è maggiore di 5" "x è maggiore di 5" ## [10] "x è maggiore di 5" ``` --- # `ifelse()` Come anche per gli `if statements` normali, posso creare degli `ifelse() nested` quando ho bisogno di testare più alternative. Immaginiamo di avere una colonna/vettore `age` e voler creare un altro vettore dove l'età è divisa in 3 fascie, bambino, adulto, anziano: ```r age <- round(runif(50, 3, 80)) age_ifelse <- ifelse(age < 18, yes = "bambino", no = ifelse( age >= 18 & age < 60, "adulto", "anziano" )) ``` --- # `dplyr::case_when()` Quando le condizioni da testare sono numerose (indicativamente > 3) può essere tedioso scrivere molti `ifelse()` multipli. Possiamo allora usare la funzione `dplyr::case_when()` del pacchetto `dplyr` che è una generalizzazione di `ifelse()`: ```r age_case_when <- case_when(age < 18 ~ "bambino", age >= 18 & age < 60 ~ "adulto", TRUE ~ "anziano") # con TRUE si identifica "tutto il resto" in modo da non lasciare valori scoperti (ATTENZIONE) ``` I due risultati sono identici: ```r all.equal(age_case_when, age_ifelse) ``` ``` ## [1] TRUE ``` --- class: section, center, middle # Programmazione iterativa --- # Programmazione iterativa Il concetto di *iterazione* è alla base di qualsiasi operazione nei linguaggi di programmazione. In R molte delle operazioni sono vettorizzate. Questo rende il linguaggio più efficiente e pulito MA nasconde il concetto di *iterazione* --- # Programmazione iterativa Esempio: se io vi chiedo di usare la funzione `print()` per scrivere `"hello world"` nella console 10 volte, come fate? ```r msg <- "Hello World" print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` # Programmazione iterativa Quello che ci manca è un modo di ripetere una certa operazione, senza effettivamente ripetere il codice manualmente. Ci sono vari costrutti che ci permettono di ripetere operazioni: - Cicli `for` - Cicli `while` - `*apply` family - altri --- # For <img src="data:image/png;base64,#img/for_loop.png" width="40%" style="display: block; margin: auto;" /> --- # For La scrittura di un ciclo `for` è: ```r for(i in 1:n){ # operazioni } ``` --- # Scomponiamo il ciclo for Ci sono diversi elementi: - `for(){}`: è l'implementazione in R (in modo simile all'`if statement`) - `i`: questo viene chiamato *iteratore* o *indice*. E' un indice generico che può assumere qualsiasi valore e nome. Per convenzione viene chiamato `i`, `j` etc. Questo tiene conto del numero di iterazioni che il nostro ciclo deve fare - `in <valori>`: questo indica i valori che assumerà l'*iteratore* all'interno del ciclo - `{ # operazioni }`: sono le operazioni che i ciclo deve eseguire --- # Hello world come ciclo for ```r # vogliamo che il ciclo ripeta l'operazione 10 volte for(i in 1:10){ print("hello world") } ``` ``` ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ``` --- # Ma l'iteratore? La potenza del ciclo `for` sta nel fatto che l'iteratore `i` assume i valori del vettore specificato dopo `in`, uno alla volta: ```r for(i in 1:10){ print(i) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ## [1] 6 ## [1] 7 ## [1] 8 ## [1] 9 ## [1] 10 ``` --- # For con iteratore vs senza Questa è una distinzione importante quanto sottile, notate la differenza tra questi due cicli: .pull-left[ ```r vec <- 1:5 for(i in 1:length(vec)){ print(vec[i]) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] .pull-right[ ```r vec <- 1:5 for(i in vec){ print(i) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] --- # While Il ciclo `while` è una versione più generale del ciclo for. Per funzionare utilizza una *condizione logica* e non un iteratore e un range di valori come nel `for`. ```r while(condizione){ # operazioni } ``` Dove il ciclo continuearà fino a che la `condizione` è vera --- # While - (Fun) Provate a scrivere questo ciclo `while` e vedere cosa succede: ```r x <- 10 while (x < 15) { print(x) } ``` Chi mi sa spiegare il risultato? --- # While Questo esercizio è utile per capire che il `while` è un ciclo non pre-determinato e quindi necessita sempre di un modo per essere interrotto, facendo diventare la condizione falsa. ```r x <- 5 while (x < 15) { print(x) x <- x + 1 } ``` ``` ## [1] 5 ## [1] 6 ## [1] 7 ## [1] 8 ## [1] 9 ## [1] 10 ## [1] 11 ## [1] 12 ## [1] 13 ## [1] 14 ``` --- # Applicazioni dei cicli Gli esempi finora sono semplici ma poco utili. Quando il queste strutture iterative sono veramente utili? Molte delle funzioni che utilizziamo come ad esempio `sum()`, `mean()`, etc. hanno al loro interno una sturttura iterativa Immaginiamo di non avere la funzione `sum()` e di volerla ricreare, come facciamo? Idee? --- # Somma come iterazione Scomponiamo concettualmente la somma, sommiamo i numeri da 1 a 10: - prendo il primo e lo sommo al secondo (`somma = 1 + 2`) - prendo la `somma` e la sommo al 3 elemento `somma = somma + 3` - ... In pratica abbiamo: - il nostro vettore da sommare - un oggetto `somma` che accumula progressivamente le somme precedenti --- # Somma come iterazione ```r somma <- 0 # inizializziamo la somma a 0 x <- 1:10 for(i in seq_along(x)){ somma <- somma + x[i] } ``` --- # Somma come iterazione Mettiamo tutto dentro una funzione ```r my_sum <- function(x){ somma <- 0 # inizializziamo la somma a 0 for(i in seq_along(x)){ somma <- somma + x[i] } return(somma) } x <- rnorm(100) my_sum(x) ``` ``` ## [1] -2.34263 ``` ```r sum(x) ``` ``` ## [1] -2.34263 ``` --- class: section, center, middle # Ma in R c'è qualcosa di meglio... --- # Ma in R c'è qualcosa di meglio... In R, l'utilizzo **esplicito** dei cicli `for` non è molto diffuso, per 2 motivi: - R è un linguaggio fortemente **funzionale** - R è un linguaggio spesso **vettorizzato** - I cicli `for` sono molto verbosi e non sempre leggibili - I cicli `for` in R, se non scritti bene, possono essere *estremamente lenti* --- class: section, center, middle # `*apply` family --- # `*apply` family Immaginate di avere una `lista` di vettori, e di voler applicare la stessa funzione/i ad ogni elemento della lista. Come fare? ^[1] - applico manualmente la funzione selezionando gli elementi - ciclo `for` che itera sugli elementi della lista e applica la funzione/i - ... ```r my_list <- list( vec1 <- rnorm(100), vec2 <- runif(100), vec3 <- rnorm(100), vec4 <- rnorm(100) ) ``` .footnote[ Hadley Wickam - The joy of functional programming - [link](https://www.youtube.com/watch?v=bzUmK0Y07ck&t=1453s) ] --- # `*apply` family Applichiamo `media`, `mediana` e `deviazione standard`: .pull-left[ ```r means <- vector(mode = "numeric", length = length(my_list)) medians <- vector(mode = "numeric", length = length(my_list)) stds <- vector(mode = "numeric", length = length(my_list)) for(i in 1:length(my_list)){ means[i] <- mean(my_list[[i]]) medians[i] <- median(my_list[[i]]) stds[i] <- sd(my_list[[i]]) } ``` ] .pull-right[ ```r means ``` ``` ## [1] 0.05303314 0.46612459 -0.06516224 0.06488834 ``` ```r medians ``` ``` ## [1] 0.05591462 0.41631825 -0.12156740 0.04863822 ``` ```r stds ``` ``` ## [1] 0.9988907 0.2939826 0.9210078 0.9263469 ``` ] --- # `*apply` family Funziona tutto! ma: - il `for` è molto laborioso da scrivere gli indici sia per la lista che per il vettore che stiamo popolando - dobbiamo *pre-allocare delle variabili* (per il motivo della velocità che dicevo) - 8 righe di codice (per questo esempio semplice) --- # `*apply` family In R è presente una famiglia di funzioni `*apply` come `lapply`, `sapply`, etc. che permettono di ottenere lo stesso risultato in modo più conciso, rapido e semplice: ```r means <- sapply(my_list, mean) medians <- sapply(my_list, median) stds <- sapply(my_list, sd) means ``` ``` ## [1] 0.05303314 0.46612459 -0.06516224 0.06488834 ``` ```r medians ``` ``` ## [1] 0.05591462 0.41631825 -0.12156740 0.04863822 ``` ```r stds ``` ``` ## [1] 0.9988907 0.2939826 0.9210078 0.9263469 ``` --- # `*apply` family - Bonus Prima di introdurre l'`*apply` family un piccolo bonus. Sfruttando il fatto che in R **tutto è un oggetto** possiamo scrivere in modo ancora più conciso: ```r my_funs <- list(median = median, mean = mean, sd = sd) lapply(my_list, function(vec) sapply(my_funs, function(fun) fun(vec))) ``` ``` ## [[1]] ## median mean sd ## 0.05591462 0.05303314 0.99889070 ## ## [[2]] ## median mean sd ## 0.4163183 0.4661246 0.2939826 ## ## [[3]] ## median mean sd ## -0.12156740 -0.06516224 0.92100776 ## ## [[4]] ## median mean sd ## 0.04863822 0.06488834 0.92634693 ``` Amazing! ora cerchiamo di dare un senso a queste righe di codice! --- # `*apply` family ```r *apply(<lista>, <funzione>) ``` - cosa può essere la `lista`? - lista - dataframe - vettore - cosa può essere la `funzione`? - funzione *base* o importata *pacchetto* - funzione *custom* - funzione *anonima* --- # `*apply` family - intuizione Prima di analizzare l'`*apply` family, credo sia utile un ulteriore parallelismo con il ciclo `for` che abbiamo visto. `*apply` non è altro che un ciclo `for`, leggermente semplificato: .pull-left[ ```r vec <- 1:5 for(i in vec){ print(i) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] .pull-right[ ```r vec <- 1:5 res <- sapply(vec, print) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] --- # `*apply` family - spoiler funzione anonima Quindi come il ciclo `for` scritto come `i in vec` assegna al valore `i` un elemento per volta dell'oggetto `vec`, internamente le funzioni `*apply` prendono il primo elemento dell'oggetto in input (`lista`) e applicano direttamente la funzione che abbiamo scelto. C'è un modo per rendere esplicito questo, anche nelle funzioni `*apply`: .pull-left[ ```r vec <- 1:5 res <- sapply(vec, print) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] .pull-right[ ```r vec <- 1:5 res <- sapply(vec, function(i) print(i)) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] --- # `*apply` e funzioni custom ```r center_var <- function(x){ x - mean(x) } my_list <- list( vec1 = runif(10), vec2 = runif(10), vec3 = runif(10) ) lapply(my_list, center_var) ``` ``` ## $vec1 ## [1] -0.3906830676 -0.3275451871 -0.1561948323 -0.0658501800 -0.0008410309 ## [6] 0.2206063991 0.4816443975 -0.3087577742 0.2128094650 0.3348118103 ## ## $vec2 ## [1] -0.4624155238 0.2426654047 -0.1109394866 -0.4373845837 0.0002660305 ## [6] 0.1011536252 0.2025211398 0.4953495616 -0.2519787866 0.2207626191 ## ## $vec3 ## [1] -0.04591725 -0.51115540 0.10209418 0.21929009 0.32659879 ## [6] -0.06328305 -0.40574902 0.36239579 0.18990424 -0.17417836 ``` --- # `*apply` e funzioni anonime Una funzione anonima è una funzione non salvata in un oggetto ma scritta per essere **eseguita direttamente**, all'interno di altre funzioni che lo permettono: ```r lapply(my_list, function(x) x - mean(x)) ``` ``` ## $vec1 ## [1] -0.3906830676 -0.3275451871 -0.1561948323 -0.0658501800 -0.0008410309 ## [6] 0.2206063991 0.4816443975 -0.3087577742 0.2128094650 0.3348118103 ## ## $vec2 ## [1] -0.4624155238 0.2426654047 -0.1109394866 -0.4373845837 0.0002660305 ## [6] 0.1011536252 0.2025211398 0.4953495616 -0.2519787866 0.2207626191 ## ## $vec3 ## [1] -0.04591725 -0.51115540 0.10209418 0.21929009 0.32659879 ## [6] -0.06328305 -0.40574902 0.36239579 0.18990424 -0.17417836 ``` Come per i cicli `for` (ricordo che `*apply` e `for` sono identici), `x` è solo un placeholder (analogo di `i`) e può essere qualsiasi lettera o nome --- # Tutte le tipologie di `*apply` Vediamo tutti i tipi di `*apply` che ci sono. Alcuni sono più *utili* altri più *robusti* e altri ancora poco utilizzati: - `lapply()`: la funzione di base - `sapply()`: `simplified-apply` - `tapply()`: poco utilizzata, utile con i *fattori* - `apply()`: utile per i *dataframe/matrici* - `mapply()`: versione multivariata, utilizza *più liste contemporaneamente* - `vapply()`: utilizzata dentro le funzioni e pacchetti --- # `lapply` `lapply` sta per list-apply e restituisce sempre una lista, applicando la funzione ad ogni elemento della lista in input: ```r res <- lapply(my_list, mean) res ``` ``` ## $vec1 ## [1] 0.4751918 ## ## $vec2 ## [1] 0.490057 ## ## $vec3 ## [1] 0.5304402 ``` ```r class(res) ``` ``` ## [1] "list" ``` --- # `sapply` `sapply` sta per simplified-apply e (cerca) di restituire una versione più semplice di una lista, applicando la funzione ad ogni elemento della lista in input: ```r res <- sapply(my_list, mean) res ``` ``` ## vec1 vec2 vec3 ## 0.4751918 0.4900570 0.5304402 ``` ```r class(res) ``` ``` ## [1] "numeric" ``` --- # `apply` `apply` funziona in modo specifico per dataframe o matrici, applicando una funzione alle righe o alle colonne: - `apply(dataframe, index, fun)` ```r # index 1 = riga, 2 = colonna my_dataframe <- data.frame(my_list) head(my_dataframe) ``` ``` ## vec1 vec2 vec3 ## 1 0.0845087 0.02764149 0.48452293 ## 2 0.1476466 0.73272242 0.01928477 ## 3 0.3189969 0.37911753 0.63253435 ## 4 0.4093416 0.05267243 0.74973027 ## 5 0.4743507 0.49032305 0.85703896 ## 6 0.6957982 0.59121064 0.46715712 ``` ```r apply(my_dataframe, 1, mean) ``` ``` ## [1] 0.1988910 0.2998846 0.4435496 0.4039148 0.6072376 0.5847220 0.5913685 ## [8] 0.6815588 0.5488080 0.6256950 ``` ```r apply(my_dataframe, 2, mean) ``` ``` ## vec1 vec2 vec3 ## 0.4751918 0.4900570 0.5304402 ``` ```r apply(my_dataframe, 2, center_var) ``` ``` ## vec1 vec2 vec3 ## [1,] -0.3906830676 -0.4624155238 -0.04591725 ## [2,] -0.3275451871 0.2426654047 -0.51115540 ## [3,] -0.1561948323 -0.1109394866 0.10209418 ## [4,] -0.0658501800 -0.4373845837 0.21929009 ## [5,] -0.0008410309 0.0002660305 0.32659879 ## [6,] 0.2206063991 0.1011536252 -0.06328305 ## [7,] 0.4816443975 0.2025211398 -0.40574902 ## [8,] -0.3087577742 0.4953495616 0.36239579 ## [9,] 0.2128094650 -0.2519787866 0.18990424 ## [10,] 0.3348118103 0.2207626191 -0.17417836 ``` --- # `tapply` `tapply` permette di applicare una funzione ad un *vettore*, dividendo questo vettore in base ad una variabile categoriale: - `tapply(dataframe, index, fun)`: dove `index` è un vettore di stringa o un fattore ```r vec <- rnorm(75) index <- rep(c("a", "b", "c"), each = 25) tapply(vec, index, mean) ``` ``` ## a b c ## -0.1104789 -0.2964323 -0.1001709 ``` --- # `vapply` `vapply` è una versione più *solida* delle precedenti dal punto di vista di programmazione. In pratica permette (e richiede) di specificare in anticipo la tipologia di dato che ci aspettiamo come risultato `vapply(X = , FUN = , FUN.VALUE = ,... )` ```r vapply(my_list, FUN = mean, FUN.VALUE = numeric(length = 1)) ``` ``` ## vec1 vec2 vec3 ## 0.4751918 0.4900570 0.5304402 ``` - `my_list, FUN = mean`: è esattamente uguale a `sapply/lapply` - `FUN.VALUE = numeric(length = 1)`: indica che ogni risultato è un singolo valore numerico --- # `mapply` Questa è quella più complicata ma anche molto utile. Praticamente permette di gestire più liste contemporaneamente per scenari più complessi. Ad esempio vogliamo usare la funzione `rnorm()` e generare vettori con diverse **medie** e **deviazioni stardard** in combinazione. ```r medie <- list(10, 20, 30, 40) stds <- list(1,2,3,4) mapply(function(x, y) rnorm(n = 10, mean = x, sd = y), medie, stds, SIMPLIFY = FALSE) ``` ``` ## [[1]] ## [1] 9.854157 9.997308 11.080678 9.344266 9.723272 8.209828 10.442507 ## [8] 10.486574 9.897129 9.425624 ## ## [[2]] ## [1] 18.11544 20.21906 18.86286 18.04465 17.20843 18.16961 17.61203 ## [8] 16.38828 17.96520 16.94744 ## ## [[3]] ## [1] 34.56984 27.18783 22.00491 25.73849 32.09195 31.03856 31.06448 ## [8] 31.01970 32.16401 21.27425 ## ## [[4]] ## [1] 38.15092 36.18962 42.75919 46.24862 42.63458 41.48714 42.72872 ## [8] 40.60777 42.82849 39.41327 ``` **IMPORTANTE**, tutte le liste incluse devono avere la stessa dimensione! --- # `mapply` ```r mapply(function(x, y) rnorm(n = 10, mean = x, sd = y), medie, stds, SIMPLIFY = FALSE) ``` - `function(...)`: è una funzione anonima come abbiamo visto prima che può avere *n* elementi - `rnorm(n = 10, mean = x, sd = y)`: è l'effettiva funzione anonima dove abbiamo i placeholders `x` and `y` - `medie, stds`: sono **in ordine** le liste corrispondenti ai placeholders indicati, quindi `x = medie` e `y = stds`. - `SIMPLIFY = FALSE`: semplicemente dice di restituire una lista e non cercare (come `sapply`) di semplificare il risultato --- # `mapply` come `for` Lo stesso risultato (in modo più verboso e credo meno intuitivo) si ottiene con un `for` usando più volte l'iteratore `i`: ```r medie <- list(10, 20, 30, 40) stds <- list(1,2,3,4) res <- vector(mode = "list", length = length(medie)) for(i in 1:length(medie)){ res[[i]] <- rnorm(10, mean = medie[[i]], sd = stds[[i]]) } res ``` ``` ## [[1]] ## [1] 8.730746 9.952948 9.298412 10.306775 8.453338 9.782865 9.226158 ## [8] 9.984558 12.199066 10.028404 ## ## [[2]] ## [1] 20.05111 19.94085 20.58144 16.95926 21.94485 22.55034 19.61594 ## [8] 18.74241 17.15199 18.55616 ## ## [[3]] ## [1] 28.01578 27.97848 30.97064 33.69737 29.72758 29.51960 27.41596 ## [8] 27.30659 31.57643 33.16963 ## ## [[4]] ## [1] 36.81883 40.84455 43.11287 43.22832 42.40409 36.07497 41.22779 ## [8] 47.33582 34.88712 34.41375 ``` --- class: section, center, middle # `*apply` alcune precisazioni --- # `*apply` vettore vs lista Abbiamo sempre usato esplicitamente `liste` fino ad ora, ma le funzioni `*apply` sono direttamente applicabili anche a **vettori** - se usiamo un vettore di *n* elementi, allora itereremo da `1:n` - se usiamo una lista di *n* elementi, allora iteriamo da `1:n` dove il singolo elemento può essere qualsiasi cosa ```r my_vec <- 1:5 my_list <- list(a = 1:2, b = 3:4, c = 5:6) res <- sapply(my_vec, print) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ```r res <- sapply(my_list, print) ``` ``` ## [1] 1 2 ## [1] 3 4 ## [1] 5 6 ``` --- # `*apply` come un `for` Nulla ci vieta (ma perdiamo l'aspetto intuitivo e conciso) di usare le funzioni `*apply` esattamente come un ciclo `for`, usando un **iteratore**: ```r medie <- c(10, 20, 30, 40) stds <- c(1,2,3,4) res <- lapply(1:length(medie), function(i){ rnorm(n = 10, mean = medie[i], sd = stds[i]) }) ``` Trovo tuttavia più chiara l'alternativa usando `mapply`: ```r mapply(function(x, y) rnorm(n = 10, mean = x, sd = y), medie, stds, SIMPLIFY = FALSE) ``` --- class: section, center, middle # Extra: `purrr::map*` --- # Extra: `purrr::map*` .pull-left[ <img src="data:image/png;base64,#img/purrr.svg" width="60%" style="display: block; margin: auto;" /> ] .pull-right[ Senza addentrarci troppo in questo modo, c'è una famiglia di funzioni che una volta imparato `*apply` vi consiglio di usare perchè più consistenti e intuitive, la `map*` family. ] --- # Extra: `purrr::map*` Per usare `purrr::map*` è sufficiente installare il pacchetto `purrr` con `install.packages("purrr")` ed iniziare ad usare le nuove funzioni. La sintassi è esattamente la stessa di `*apply` (qualche modifica ma potete usare la stessa) ma invece che usare una funzione per tutto, abbiamo molte funzioni per ogni casistica: - `map(lista, funzione)` è l'analogo di `lapply()` e fornisce sempre una lista - `map_dbl(lista, funzione)` applica la funzione ad ogni elemento e **si aspetta che** il risultato sia un vettore di *double* - `map_lgl(lista, funzione)` applica la funzione ad ogni elemento e **si aspetta che** il risultato sia un vettore *logico* - `map2/pmap_*` sono rispettivamente applicare la funzione a 2/n liste (analogo di `mapply()`) --- class: section, center, middle # Extra: `replicate()` and `repeat()` --- # Extra: `replicate()` and `repeat()` Ci sono altre due funzioni in R che permettono di *iterare*. Sono meno utilizzate perchè si ottengono gli stessi risultati usando un semplice `for` o `*apply`. - `replicate()` permette di ripetere un operazione *n* volte, senza però utilizzare un `iteratore` o un `placeholder`. - `repeat()` anche repeat permette di ripetere ma fino a che non si verifica un certa condizione (**logica**). Ha una struttura simile al ciclo `while` --- class: section, center, middle # Extra: Formula syntax --- # Formula syntax In R molte operazioni vengono eseguite usando la **formula syntax** `something ~ something else` ad esempio: - modelli statistici: `lm(y ~ x, data = data)`, `t.test(y ~ factor, data = data)` - plot: `boxplot(y ~ x, data = data)` - ... In cosa consiste? --- # Formula syntax Senza andare nei dettagli tecnici, R usa una cosa che si chiama *lazy evaluation*. In altri termini "salva" delle operazioni per essere eseguite in un secondo momento. Tutti sappiamo che se scriviamo un nome (senza virgolette) e questo non è associato ad un oggetto otteniamo un errore. Tuttavia alcune funzioni come `library()` non forniscono errore. Perchè? ```r stats # errore ``` ``` ## Error in eval(expr, envir, enclos): object 'stats' not found ``` ```r library(stats) # no errore ``` --- # Formula syntax La ragione è che R è in grado di salvare un'espressione per usarla poi in uno specifico contesto (ad esempio dentro una funzione). La `formula syntax` è un esempio. Usando la tilde `~` possiamo creare delle `formule` che R può utilizzare in specifici contesti: ```r y ``` ``` ## [1] a a a a a a a a a a b b b b b b b b b b c c c c c c c c c c ## Levels: a b c ``` ```r x ``` ``` ## [1] -2.1218661746 0.9447857099 -1.4140852128 0.9207907742 -0.5606485492 ## [6] 0.1183357684 0.0004055866 0.2210919431 -0.0023129997 -0.1172141960 ## [11] 0.2015221258 1.7207025578 -0.7454439706 -0.4894311749 1.2122725090 ## [16] 1.1580786996 -0.9281901221 -1.6864421762 -0.6239150240 0.1459161106 ## [21] 0.2093735554 1.6453100622 -1.3980834995 1.7917679291 0.1359643622 ## [26] -0.6557010837 1.4894553324 0.9857083896 -1.1668587827 0.2349273772 ## [31] 0.3637474435 0.0848580716 0.1429972582 -0.3922700366 0.3336449163 ## [36] -1.7589215333 -0.3858169083 -0.2221935884 2.6707380406 -0.5303414810 ## [41] -1.8999541537 -0.7344403075 0.5050230179 -0.6252900764 0.6309774651 ## [46] 0.8083048516 0.0171785739 0.1301313312 -0.3461780253 0.8175371546 ## [51] 0.1793564253 0.9386849925 0.4701817181 2.0048178135 1.3848594920 ## [56] -2.9882100753 -1.9141335216 -2.7763186617 0.2024280430 -0.2172418171 ## [61] -0.8285111471 0.9668467255 -0.0274846988 0.1544432257 -0.6402435268 ## [66] 1.2601703970 -1.1619188527 -0.9275051738 -0.1959106993 1.4021271095 ## [71] -0.8506026493 -0.2550548408 1.4373154411 -0.5907986518 0.5134716900 ## [76] -2.5559080180 -1.0751142047 1.1297121411 -0.9783666778 -1.1772359313 ## [81] -0.5702900076 1.0758670833 0.2515597525 -0.6673320809 0.6992752190 ## [86] -0.4017313417 -0.9567788050 0.6239020296 1.1507722516 0.1575220273 ## [91] -0.8380720327 1.5775318008 0.6197120085 -0.6876128360 0.0673343141 ## [96] 1.8144532309 0.2250526510 -0.2281548016 0.5923147959 -0.5677586918 ``` ```r y ~ x ``` ``` ## y ~ x ## <environment: 0x55619213b0a0> ``` ```r my_formula <- y ~ x class(my_formula) ``` ``` ## [1] "formula" ``` --- # Formula syntax e `aggregate()` Un esempio utile è la funzione `aggregate()` molto interessante per applicare funzioni a dataframe. Immaginate di avere il dataset `iris` e calcolare la media per ogni livello del fattore `Species`: ```r tapply(iris$Sepal.Length, iris$Species) ``` ``` ## [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ## [36] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 ## [71] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 ## [106] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ## [141] 3 3 3 3 3 3 3 3 3 3 ``` ```r aggregate(Sepal.Length ~ Species, FUN = mean, data = iris) ``` ``` ## Species Sepal.Length ## 1 setosa 5.006 ## 2 versicolor 5.936 ## 3 virginica 6.588 ``` ```r # Anche creando un oggetto, ma solo come formula my_formula <- Sepal.Length ~ Species my_char <- "Sepal.Length ~ Species" aggregate(my_char, FUN = mean, data = iris) ``` ``` ## Error in inherits(by, "formula"): argument "by" is missing, with no default ``` ```r # Viene lo stesso usando $ e senza specificare data = aggregate(iris$Sepal.Length, iris$Species, FUN = mean) ``` ``` ## Error in aggregate.data.frame(as.data.frame(x), ...): 'by' must be a list ``` --- # Formula syntax e `aggregate()` Ma anche operazioni più complesse: ```r my_iris <- iris my_iris$fac <- rep(c("a", "b", "c"), 50) aggregate(Sepal.Length ~ Species + fac, mean, data = my_iris) ``` ``` ## Species fac Sepal.Length ## 1 setosa a 5.052941 ## 2 versicolor a 5.770588 ## 3 virginica a 6.756250 ## 4 setosa b 5.011765 ## 5 versicolor b 6.018750 ## 6 virginica b 6.447059 ## 7 setosa c 4.950000 ## 8 versicolor c 6.023529 ## 9 virginica c 6.570588 ``` --- # Replicate .pull-left[ `replicate(n, expr)` - `n` è il numero di ripetizioni - `expr` è la porzione di codice da ripetere ```r # Campioniamo 1000 volte da una normale e facciamo la media AKA distribuzione campionaria della media nrep <- 1000 nsample <- 30 media <- 100 ds <- 30 means <- replicate(n = nrep, expr = { mean(rnorm(nsample, media, ds)) }) ``` ] .pull-right[ <img src="data:image/png;base64,#4_programmazione_files/figure-html/unnamed-chunk-62-1.png" width="2100" style="display: block; margin: auto;" /> ] --- # `repeat()` ```r repeat { # cose da ripetere if(...){ # condizione da valutare break # ferma il loop } } ``` ```r i <- 1 repeat { print(i) i = i + 1 if(i > 3){ break } } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ``` --- # `repeat()` vs `while` <!-- TODO revise repeat vs loop --> .pull-left[ ```r i <- 1 repeat { print(i) i = i + 1 if(i > 3){ break } } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ``` ] .pull-right[ ```r i <- 1 while(i < 4){ print(i) i <- i + 1 } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ``` ] - `repeat` valuta la condizione una volta finita l'iterazione, mentre `while` all'inizio. Se la condizione non è `TRUE` all'inizio, il `while` non parte mentre `repeat` si.